#pragma once
#include "utf8.h"
#include <map>
#include <vector>
#include <string>
#include <sstream>
#include <assert.h>

namespace Json
{
    enum 
    {
        jsonnodetype_null = 1,
        jsonnodetype_bool = 2,
        jsonnodetype_number = 4,
        jsonnodetype_string = 8,
        jsonnodetype_array = 16,
        jsonnodetype_object = 32,
    };

    class Node
    {
    private:

        int _type;

        //inline void addType(int type)
        //{
        //    _type |= type;
        //}

        inline bool isType(int type) const
        {
            return (_type & type) != 0;
        }

    private:

        mutable std::map<std::string, Node> _map;
        std::vector<Node> _array;
        std::string _string;
        double _number;
        bool _bool;

    public:

        typedef std::map<std::string, Node>::const_iterator const_iterator;
        //------//
        // Null //
        //------//

        Node(void)
        {
            _type = jsonnodetype_null;
        }

        bool isNull() const
        {
            return isType(jsonnodetype_null);
        }

        //---------//
        // Boolean //
        //---------//

        Node(bool b)
        {
            _type = jsonnodetype_bool;
            _bool = b;
        }

        bool isBool() const
        {
            return isType(jsonnodetype_bool);
        }

        bool toBool(bool df = false) const
        {
            if (isBool())
            {
                return _bool;
            }
            else if (isNumber())
            {
                return toNumber(0) != 0;
            }
            else if (isString())
            {
                std::string s;
                std::transform(_string.begin(), _string.end(), std::back_inserter(s), tolower);
                return s == "true" || s == "yes";
            }
            return df;
        }

        operator bool ()
        {
            return toBool();
        }

        //--------//
        // Number //
        //--------//

        Node(double d)
        {
            _type = jsonnodetype_number;
            _number = d;
        }

        Node(float d)
        {
            _type = jsonnodetype_number;
            _number = d;
        }

        Node(int d)
        {
            _type = jsonnodetype_number;
            _number = d;
        }

        bool isNumber() const
        {
            return isType(jsonnodetype_number);
        }

        inline double toNumber(double df = 0.0) const
        {
            if (isNumber())
            {
                return _number;
            }
            else if (isString())
            {
                std::string str = _string;
                std::replace(str.begin(), str.end(), ',', '.');
                
                double number = 0;
                if (str != "")
                {
                    std::stringstream ss;
                    ss << str;
                    ss >> number;
                }
                                
                return number;
            }
            return df;
        }

        operator double ()
        {
            return toNumber();
        }

        operator float ()
        {
            return (float)toNumber();
        }

        operator int ()
        {
            return (int)toNumber();
        }

        //--------//
        // String //
        //--------//

        Node(const std::string & s)
        {
            _type = jsonnodetype_string;
            _string = s;
        }

        Node(const char * s)
        {
            _type = jsonnodetype_string;
            _string = s;
        }

        std::string toString(const std::string & df = "") const
        {
            if (isString())
            {
                return _string;
            }
            else if (isNumber())
            {
                std::stringstream ss;
                ss << _number;
                return ss.str();
            }
            return df;
        }

        operator std::string ()
        {
            return toString();
        }

        bool isString() const
        {
            return isType(jsonnodetype_string);
        }

        Node & operator << (const std::string & str)
        {
            if (isString())
            {
                _string += str;
            }
            else 
            {
                assert(false);
            }
            return *this;
        }
        
        template<typename T>
        Node & operator << (const T & val)
        {
            std::stringstream ss;
            ss << val;
            *this << ss.str();
            return *this;
        }
        //---------//
        // WString //
        //---------//

        Node(const std::wstring & s)
        {
            _type = jsonnodetype_string;
            _string = xpam::string::unicode_to_utf8(s);
        }

        Node(const wchar_t * s)
        {
            _type = jsonnodetype_string;
            _string = xpam::string::unicode_to_utf8(s);
        }

        Node & operator << (const std::wstring & str)
        {
            *this << xpam::string::unicode_to_utf8(str);
            return *this;
        }

        Node & operator << (const wchar_t * str)
        {
            *this << std::wstring(str);
            return *this;
        }


        //-------//
        // Array //
        //-------//

        Node(const std::vector<Node> & v)
        {
            _type = jsonnodetype_array;
            _array = v;
        }

        Node & operator [] (int i)
        {
            _type = jsonnodetype_array;
            if (i >= (int)_array.size())
                _array.resize(i + 1);
            return _array[i];
        }

        const Node & operator [] (int i) const
        {
            return _array[i];
        }

        void push_back(const Node & n)
        {
            _type = jsonnodetype_array;
            _array.push_back(n);
        }
        
        size_t size() const
        {
            return _array.size();
        }

        void clear() 
        {
            _array.clear();
        }

        bool isArray() const
        {
            return isType(jsonnodetype_array);
        }

        //--------//
        // Object //
        //--------//

        Node(const std::map<std::string, Node> & m)
        {
            _type = jsonnodetype_object;
            _map = m;
        }

        const Node & operator [] (const char * key) const
        {
            return _map[key];
        }

        Node & operator [] (const char * key)
        {
            _type = jsonnodetype_object;
            return _map[key];
        }

        std::map<std::string, Node>::const_iterator begin() const
        {
            return _map.begin();
        }

        std::map<std::string, Node>::const_iterator end() const
        {
            return _map.end();
        }

        bool isObject() const
        {
            return isType(jsonnodetype_object);
        }

        std::map<std::string, Node> toMap(const std::map<std::string, Node> & df = std::map<std::string, Json::Node>()) 
        {
            if (isObject())
            {
                return _map;
            }
            return df;
        }
    };

    class Json
    {
        std::string _str;
        size_t _index;

        inline bool _HasTokens()
        {
            return _index < _str.size();
        }

        inline bool _IsToken(char token)
        {
            if (!_HasTokens())
                throw std::exception("_IsToken() : _HasTokens()");
            return _str[_index] == token;
        }

        template<size_t _Size>
        inline bool _IsTokens(char (&tokens)[_Size])
        {
            for (int i = 0; i < _Size; i++)
            {
                if (_IsToken(tokens[i]))
                {
                    return true;
                }
            }
            return false;
        }

        inline void _GetToken(char token)
        {
            if (_IsToken(token))
            {
                _index++;
                return;
            }
            throw std::exception((std::string("_GetToken(token) : ") + token).c_str());
        }

        inline char _GetNextCharToken()
        {
            char ch = _str[_index];
            _index ++;
            return ch;
        }

        template<size_t _Size>
        inline char _GetTokens(char (&tokens)[_Size])
        {
            for (int i = 0; i < _Size; i++)
            {
                if (_IsToken(tokens[i]))
                {
                    _GetToken(tokens[i]);
                    return tokens[i];
                }
            }
            throw std::exception(("_GetTokens(token) : " + _str.substr(_index)).c_str());
        }

        inline void _SkipToken(char token)
        {
            if (_IsToken(token))
            {
                _GetToken(token);
            }
        }

        inline void _SkipWhiteSpace()
        {
            _SkipToken(' ');
        }

        inline Node _DecodeString()
        {
            _SkipWhiteSpace();
            _GetToken('"');
            std::string retval = "";
            while (true)
            {
                char ch = _GetNextCharToken();
                if (ch == '"')
                    break;

                if (ch == '\\')
                {
                    ch = _GetNextCharToken();
                    if (ch == '"')
                    {
                        retval += ch;
                        continue;
                    }
                    else if (ch == 'u')
                    {
                        std::stringstream ss;
                        ss << _GetNextCharToken();
                        ss << _GetNextCharToken();
                        ss << _GetNextCharToken();
                        ss << _GetNextCharToken();
                        int code;
                        ss >> std::hex >> code;

                        retval += (char)(192 + code / 64);
                        retval += (char)(128 + code % 64);
                        continue;
                    }
                    else
                    {
                        retval += ch;
                        continue;
                    }
                }
                // TODO
                retval += ch;
            }
            return Node(retval);
        }

        inline Node _DecodeNumber()
        {
            std::string retval = "";
            while (true)
            {
                char chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '-', '+'};
                if (_IsTokens(chars))
                {
                    retval += _GetTokens(chars);
                }
                else
                {
                    break;
                }
            }
            std::stringstream ss;
            ss << retval;
            double d = 0;
            ss >> d;
            return Node(d);
        }

        inline Node _DecodeArray()
        {
            _GetToken('[');
            std::vector<Node> retval;
            bool complete = false;
            while (true)
            {
                if (_IsToken(']'))
                {
                    break;
                }
                retval.push_back(_DecodeValue());
                _SkipToken(',');
            }
            _GetToken(']');
            return Node(retval);
        }

        inline Node _DecodeObject()
        {
            _GetToken('{');
            std::map<std::string, Node> retval;
            bool complete = false;

            while (true)
            {
                if (_IsToken('}'))
                {
                    break;
                }
                Node key = _DecodeString();
                _SkipWhiteSpace();
                _GetToken(':');
                retval[key.toString()] = _DecodeValue();
                _SkipToken(',');
            }
            _GetToken('}');
            return Node(retval);
        }

        inline Node _DecodeValue()
        {
            _SkipWhiteSpace();
            if (_IsToken('"'))
                return _DecodeString();

            if (_IsToken('['))
                return _DecodeArray();

            if (_IsToken('{'))
                return _DecodeObject();

            if (_IsToken('n'))
            {
                _GetToken('n');
                _GetToken('u');
                _GetToken('l');
                _GetToken('l');
                return Node();
            }

            if (_IsToken('t'))
            {
                _GetToken('t');
                _GetToken('r');
                _GetToken('u');
                _GetToken('e');
                return Node(true);
            }

            if (_IsToken('f'))
            {
                _GetToken('f');
                _GetToken('a');
                _GetToken('l');
                _GetToken('s');
                _GetToken('e');
                return Node(false);
            }
            char nums[] = {'-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
            if (_IsTokens(nums))
                return _DecodeNumber();

            throw std::exception((std::string("Json::_DecodeValue() : ") + _str.substr(_index)).c_str());
        }

    public:
        inline Node Decode(const std::string & s)
        {
            _str = s;
            _index = 0;
            return _DecodeValue();
        }

        inline std::string Encode(const Node & n)
        {
            std::stringstream ss;
            if (n.isNull())
                ss << "null";
            else if (n.isBool())
                ss << (n.toBool() ? "true" : "false");
            else if (n.isString() || n.isNumber())
            {
                ss << "\"";
                std::string s = n.toString();
                for (std::string::const_iterator it = s.begin(); it != s.end(); it++)
                {
                    if ((*it) == '"')
                    {
                        ss << "\\\"";
                    }
                    else if ((*it) == '\\')
                    {
                        ss << "\\\\";
                    }
                    else
                    {
                        ss << (*it);
                    }
                }

                ss << "\"";
            }
            else if (n.isArray())
            {
                ss << "[";
                for (int i = 0, m = n.size(); i < m; i++)
                {
                    if (i != 0)
                        ss << ",";
                    ss << Encode(n[i]);
                }
                ss << "]";
            }
            else if (n.isObject())
            {
                ss << "{";
                for (std::map<std::string, Node>::const_iterator it = n.begin(); it != n.end(); it++)
                {
                    if (it != n.begin())
                        ss << ",";
                    ss << Encode(it->first) << ":" << Encode(it->second);
                }
                ss << "}";
            }


            return ss.str();
        }
    };
}